package lol.clann.minecraft.springboot.api.model.core;

import com.ruoshui.utils.java.exception.ExceptionUtils;
import com.ruoshui.utils.java.io.file.FileUtils;
import com.ruoshui.utils.java.io.jar.JarUtils;
import lol.clann.minecraft.springboot.SpringBootPlugin;
import lol.clann.minecraft.springboot.api.annotation.reload.PostReload;
import lol.clann.minecraft.springboot.api.annotation.reload.PreReload;
import lol.clann.minecraft.springboot.api.spi.plugin.PluginDefine;
import lol.clann.minecraft.springboot.api.utils.CallbackUtils;
import lol.clann.minecraft.springboot.api.utils.ConfigUtils;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.bukkit.Bukkit;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import relocate.ninja.leaping.configurate.ConfigurationOptions;
import relocate.ninja.leaping.configurate.commented.CommentedConfigurationNode;
import relocate.ninja.leaping.configurate.hocon.HoconConfigurationLoader;
import relocate.ninja.leaping.configurate.loader.ConfigurationLoader;

import java.io.IOException;
import java.nio.file.Path;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

/**
 * SBP插件实例
 *
 * @author pyz
 * @date 2019/5/1 9:59 PM
 */
@Getter
@Setter(AccessLevel.PRIVATE)
@ToString
public class Plugin {
    private static final ConfigurationOptions option = ConfigurationOptions.defaults().setShouldCopyDefaults(true);

    /**
     * 插件id
     */
    private String id;
    /**
     * 插件名字
     */
    private String displayName;
    private String version;
    /**
     * jar文件
     */
    private Path file;
    /**
     * 数据文件
     */
    private Path dataFolder;
    /**
     * 配置文件
     */
    private Path config;
    private CommentedConfigurationNode configurationNode;
    private ConfigurationLoader<CommentedConfigurationNode> configurationLoader;
    private Set<String> basePackages;
    /**
     * 该插件的所有类
     */
    private Set<String> classes;
    private List<Object> beans;

    /**
     * beans是按加载顺序添加的,所以重载时直接按顺序重载就行了
     *
     * @throws IOException
     */
    public void reload() throws IOException {
        configurationNode = configurationLoader.load(option);
        for (Object bean : beans) {
            try {
                CallbackUtils.invok(bean, PreReload.class);
                ConfigUtils.resolve(this, bean);
                CallbackUtils.invok(bean, PostReload.class);
            } catch (Throwable e) {
                ExceptionUtils.throwException(e);
            }
        }
        configurationLoader.save(configurationNode);
    }

    public void directSaveConfig() {
        try {
            configurationLoader.save(configurationNode);
        } catch (IOException e) {
            ExceptionUtils.throwException(e);
        }
    }

    public void saveConfig() {
        try {
            for (Object o : getBeans()) {
                ConfigUtils.saveConfig(configurationNode, o);
            }
            configurationLoader.save(configurationNode);
        } catch (Throwable e) {
            ExceptionUtils.throwException(e);
        }
    }

    public static Plugin from(PluginDefine pluginDefine, Map<String, Plugin> container) throws Exception {
        Path jarPath;
        if (pluginDefine instanceof SpringBootPlugin) {
            jarPath = JarUtils.getJar(Bukkit.getPluginManager().getPlugin("SpringBootPlugin").getClass());
        } else {
            jarPath = JarUtils.getJar(pluginDefine.getClass());
        }
        Plugin plugin = new Plugin();
        container.put(pluginDefine.getId(), plugin);
        plugin.setId(pluginDefine.getId());
        plugin.setDisplayName(pluginDefine.getDisplayName());
        plugin.setVersion(pluginDefine.getVersion());
        plugin.setFile(jarPath);
        plugin.setDataFolder(plugin.getFile().getParent().resolve(plugin.getId()));
        plugin.setConfig(plugin.getDataFolder().resolve("config.conf"));
        FileUtils.createFileIfNotExist(plugin.getConfig().toFile());

        plugin.setConfigurationLoader(HoconConfigurationLoader.builder()
                .setPath(plugin.getConfig())
                .build());
        plugin.setConfigurationNode(plugin.getConfigurationLoader().load(option));
        plugin.setBasePackages(pluginDefine.getBasePackages() == null ? new HashSet<>() : pluginDefine.getBasePackages());
        plugin.setClasses(new HashSet<>());

        if (pluginDefine instanceof SpringBootPlugin) {
            CachingMetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory();
//            SBP要特殊处理
            PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(Plugin.class.getClass().getClassLoader());
            Resource[] rs = resolver.getResources("classpath*:lol/clann/minecraft/springboot/**/*.class");
            for (Resource r : rs) {
                MetadataReader metadata = metadataReaderFactory.getMetadataReader(r);
                plugin.getClasses().add(metadata.getClassMetadata().getClassName());

            }
        } else {
            ZipInputStream in = new ZipInputStream(pluginDefine.getClass().getProtectionDomain().getCodeSource().getLocation().openStream());
            ZipEntry entry;
            while ((entry = in.getNextEntry()) != null) {
                if (entry.isDirectory() || !entry.getName().endsWith(".class")) {
                    continue;
                }
                String name = entry.getName();
                name = name.replace("/", ".");
                if (name.startsWith(".")) {
                    name = name.substring(1);
                }
                for (String basePackage : plugin.getBasePackages()) {
                    if (name.startsWith(basePackage + ".")) {
                        plugin.getClasses().add(name.substring(0, name.length() - 6));
                        break;
                    }
                }
            }
            in.close();
        }
        plugin.setBeans(new ArrayList<>());
        return plugin;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        Plugin plugin = (Plugin) o;
        return Objects.equals(id, plugin.id);
    }

    @Override
    public int hashCode() {

        return Objects.hash(id);
    }
}